﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections.Specialized;

namespace Elentok.Common.Collections
{
  /// <summary>
  /// This class extends the ObservableCollection class and listens
  /// to the PropertyChanged event of each item
  /// </summary>
  /// <typeparam name="T"></typeparam>
  public class VeryObservableCollection<T> : ObservableCollection<T>
  {
    #region Overrides
    /// <summary>
    /// Removes all items from the collection
    /// </summary>
    protected override void ClearItems()
    {
      OnClearing();

      foreach (T item in this)
        DisconnectItem(item);

      base.ClearItems();
    }

    /// <summary>
    /// Insert an item into the collection at the specified index
    /// </summary>
    /// <param name="index"></param>
    /// <param name="item"></param>
    protected override void InsertItem(int index, T item)
    {
      ConnectItem(item);
      base.InsertItem(index, item);
    }

    /// <summary>
    /// Removes the item at the specified index of the collection
    /// </summary>
    /// <param name="index"></param>
    protected override void RemoveItem(int index)
    {
      DisconnectItem(this[index]);
      base.RemoveItem(index);
    }

    /// <summary>
    /// Replaces the element at the specified index
    /// </summary>
    /// <param name="index"></param>
    /// <param name="item"></param>
    protected override void SetItem(int index, T item)
    {
      var oldItem = this[index];
      DisconnectItem(oldItem);
      base.SetItem(index, item);
      ConnectItem(item);
    }
    #endregion

    #region Connect/Disconnect Items
    /// <summary>
    /// Connects to the "PropertyChanged" event of the specified item
    /// </summary>
    /// <param name="item"></param>
    protected virtual void ConnectItem(T item)
    {
      var notifier = item as INotifyPropertyChanged;
      if (notifier != null)
        notifier.PropertyChanged += new PropertyChangedEventHandler(Item_PropertyChanged);

      var notifier2 = item as INotifyPropertyChanging;
      if (notifier2 != null)
        notifier2.PropertyChanging += new PropertyChangingEventHandler(Item_PropertyChanging);
    }

    /// <summary>
    /// Disconnects from the "PropertyChanged" event of the specified item
    /// </summary>
    /// <param name="item"></param>
    protected virtual void DisconnectItem(T item)
    {
      var notifier = item as INotifyPropertyChanged;
      if (notifier != null)
        notifier.PropertyChanged -= new PropertyChangedEventHandler(Item_PropertyChanged);

      var notifier2 = item as INotifyPropertyChanging;
      if (notifier2 != null)
        notifier2.PropertyChanging -= new PropertyChangingEventHandler(Item_PropertyChanging);

    }
    #endregion

    #region Events & Handlers
    private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      var handler = ItemPropertyChanged;

      if (handler != null)
        handler(this, new ItemPropertyChangedEventArgs<T>((T)sender, e.PropertyName));
    }

    private void Item_PropertyChanging(object sender, PropertyChangingEventArgs e)
    {
      var handler = ItemPropertyChanging;

      if (handler != null)
        handler(this, new ItemPropertyChangingEventArgs<T>((T)sender, e.PropertyName));
    }


    /// <summary>
    /// This event is raised whenever one of the items in the collection raises the "PropertyChanged" event
    /// </summary>
    public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;

    /// <summary>
    /// This event is raised whenever one of the items in the collection raises the "PropertyChanged" event
    /// </summary>
    public event EventHandler<ItemPropertyChangingEventArgs<T>> ItemPropertyChanging;

    /// <summary>
    /// This event is raised before all of the items are cleared from the collection
    /// </summary>
    public event EventHandler Clearing;

    /// <summary>
    /// Raise the <see cref="Clearing"/> event
    /// </summary>
    protected void OnClearing()
    {
      EventHandler handler = Clearing;
      if (handler != null)
        handler(this, EventArgs.Empty);
    }
    #endregion
  }

  /// <summary>
  /// The event arguments for the "ItemPropertyChanged" event
  /// (raised by the VeryObservableCollection class)
  /// </summary>
  /// <typeparam name="T"></typeparam>
  public class ItemPropertyChangedEventArgs<T> : EventArgs
  {
    /// <summary>
    /// Gets the item
    /// </summary>
    public T Item { get; private set; }
    /// <summary>
    /// Gets the property that changed
    /// </summary>
    public string PropertyName { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs&lt;T&gt;"/>
    /// </summary>
    /// <param name="item"></param>
    /// <param name="propertyName"></param>
    public ItemPropertyChangedEventArgs(T item, string propertyName)
    {
      this.Item = item;
      this.PropertyName = propertyName;
    }
  }

  /// <summary>
  /// The event arguments for the "ItemPropertyChanging" event
  /// (raised by the VeryObservableCollection class)
  /// </summary>
  /// <typeparam name="T"></typeparam>
  public class ItemPropertyChangingEventArgs<T> : EventArgs
  {
    /// <summary>
    /// Gets the item
    /// </summary>
    public T Item { get; private set; }
    /// <summary>
    /// Gets the property that changed
    /// </summary>
    public string PropertyName { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="ItemPropertyChangingEventArgs&lt;T&gt;"/>
    /// </summary>
    /// <param name="item"></param>
    /// <param name="propertyName"></param>
    public ItemPropertyChangingEventArgs(T item, string propertyName)
    {
      this.Item = item;
      this.PropertyName = propertyName;
    }
  }

}
